import EXIF from '@nuofe/exif-js';

const FILETYPE = "FILETYPE"; // 文件类型错误

const IMAGEERROR = "IMAGEERROR"; // 转换图片文件错误

const COMPRESSERROR = "COMPRESSERROR"; // 图片文件压缩错误

const UPLOADERROR = "UPLOADERROR"; // 图片上传出错

// 超时状态码
const STATIC_TIMEOUT = 502;
// 上传响应失败
const STATIC_RESPONSE_ERROR = 999;
// 上传文件异常
const STATIC_FILE_ERROR = 1000;
// 图片压缩异常
const STATIC_COMPRESS_ERROR = 1001;
// 图片转换异常
const STATIC_EXCHANGE_ERROR = 1002;

var createImage = (base64, callback) =>
  new Promise((resolve, reject) => {
    let image = new Image();
    image.onload = () => {
      resolve(image);
      image = null;
    };
    // 图片加载出错
    image.onerror = () => {
      reject({
        message: "文件转换图片出错",
        errorType: IMAGEERROR,
        status: STATIC_EXCHANGE_ERROR
      });
    };
    image.src = base64;
  });

/**
 * 计算base64长度
 * @param {DataBase} base64
 */
const calcBASE64length = base64 =>
  base64.length - (base64.length / 8) * 2;

// 默认最大质量
const defaultMaxQuality = 100;
// 默认最小的压缩质量
const defaultMinQuality = 0;
const CANVAS = document.createElement("canvas");
const CTX = CANVAS.getContext("2d");

/**
 * 压缩图片
 * @param {*} param0
 */
function imageTODataURL({ img, quality, fileType }) {
  const width = (CANVAS.width = img.width);
  const height = (CANVAS.height = img.height);
  CTX.clearRect(0, 0, width, height);
  CTX.drawImage(img, 0, 0, width, height);
  const base64 = CANVAS.toDataURL(fileType, quality / 100);
  CANVAS.width = 0;
  CANVAS.height = 0;
  return base64;
}

/**
 * 图片压缩算法
 * @param {*} param0
 */
const compressImage = ({
  base64: base64Image,
  maxSize,
  fileType,
  quality: defaultQuality
}) => {
  let compressBase64 = base64Image;
  // 最大的压缩率
  let compressMaxQuality = defaultMaxQuality;
  // 最小压缩率
  let compressMinQuality = defaultMinQuality;
  // 压缩得到最小的图片base64
  let minCompressBase = null;
  return new Promise((resolve, reject) => {
    const compressRecursion = ({ base64, quality }) => {
      try {
        createImage(base64)
          .then(image => {
            // 将图片的类型统一转换 指定类型 默认 JPEG，
            compressBase64 = imageTODataURL({
              img: image,
              quality,
              fileType
            });

            const compressSize = calcBASE64length(compressBase64);
            if (compressSize > maxSize) {
              compressMaxQuality = quality;
              const diffQuality = compressMaxQuality - compressMinQuality;
              if (diffQuality <= 2) {
                if (!minCompressBase) {
                  // TODO: 上传图片大小限制较小时，存在一轮压缩无法达到目的.这是考虑等比例压缩图片而非质量压缩
                  resolve({
                    result: true,
                    base64: compressBase64
                  });
                } else {
                  resolve({
                    result: true,
                    base64: minCompressBase || compressBase64
                  });
                }
              } else {
                const compressQuality = Math.ceil(
                  (compressMaxQuality + compressMinQuality) / 2
                );
                compressRecursion({
                  base64: compressBase64,
                  quality: compressQuality
                });
              }
            } else {
              minCompressBase = base64;
              if (compressSize < maxSize) {
                compressMaxQuality = 2 * quality > 100 ? 100 : 2 * quality;
                compressMinQuality = quality;
                const compressQuality = Math.ceil(
                  (compressMaxQuality + compressMinQuality) / 2
                );
                compressRecursion({
                  base64: compressBase64,
                  quality: compressQuality
                });
              } else {
                resolve({ result: true, base64: compressBase64 });
              }
            }
          })
          .catch(error => {
            reject({
              errorMsg: error, // 给开发者看的
              message: "图片压缩出错了",
              errorType: COMPRESSERROR,
              status:STATIC_COMPRESS_ERROR
            });
          });
      } catch (err) {
        reject({
          errorMsg: err, // 给开发者看的
          message: "图片压缩出错了",
          errorType: COMPRESSERROR,
          status:STATIC_COMPRESS_ERROR
        });
      }
    };
    compressRecursion({ base64: base64Image, quality: defaultQuality });
  });
};

var Compress = async ({ base64, maxSize, fileType }) => {
  // 计算base64大小
  const size = calcBASE64length(base64);
  if (size > maxSize) {
    // 执行压缩
    const maxQuality = defaultMaxQuality;
    const minQuality = defaultMinQuality;
    const quality = Math.ceil((maxQuality + minQuality) / 2);
    try {
      const compressBase64 = await compressImage({
        base64,
        maxSize,
        fileType,
        quality
      });
      if (compressBase64.result) {
        const { base64: compressImage } = compressBase64;
        return { result: true, base64: compressImage };
      } else {
        return {
          errorMsg: "代码错误", // 给开发者看的
          message: "图片压缩出错了",
          errorType: COMPRESSERROR,
          status:STATIC_COMPRESS_ERROR
        };
      }
    } catch (error) {
      return {
        errorMsg: error, // 给开发者看的
        message: "图片压缩出错了",
        errorType: COMPRESSERROR,
        status:STATIC_COMPRESS_ERROR
      };
    }
  } else {
    return { result: true, base64: base64 };
  }
};

// TODO: 上传后续考虑 业务传入的 axios
class XHRUpload {
  constructor({
    changeType = "image/jpeg",
    base64 = null,
    beforeUpload,
    uploading,
    successUpload,
    errorUpload,
    xhrParam,
    uploadUrl,
    uploadTimeout,
    formdataName = "file",
    fileName='blob'
  }) {
    this.base64 = base64;
    this.beforeUpload = beforeUpload;
    this.uploading = uploading;
    this.successUpload = successUpload;
    this.errorUpload = errorUpload;
    this.changeType = changeType;
    this.uploadUrl = uploadUrl;
    this.uploadTimeout = uploadTimeout;
    // 上传的额外参数
    this.xhrParam = xhrParam;
    this.formdataName = formdataName;
    this.fileName = fileName;
    this.xhrUpload(base64);
  }

  xhrUpload(dataBase64) {
    const xhr = new XMLHttpRequest();
    const formdata = this.getFormData();
    const blob = this.dataURLtoBlob(dataBase64);
    const filename = this.fileName;
    formdata.append(this.formdataName, blob, filename);
    // 这里是重写XHR 所以这个xhrParam必须是对象
    const xhrParam = this.xhrParam;
    if (xhrParam && xhrParam instanceof Object) {
      Object.keys(this.xhrParam).map(keys => {
        formdata.append(keys, this.xhrParam[keys]);
      });
    }
    if (xhrParam && !xhrParam instanceof Object) {
      console.error("xhrParam必须是对象");
      return;
    }
    const parDate = new Date() * 1;
    xhr.open("post", `${this.uploadUrl}?_=${parDate}`);
    let timeout = this.uploadTimeout;
    const uploadTimer = setTimeout(function() {
      timeout = 0;
      xhr.abort(); // 请求中止
    }, timeout * 1000);
    xhr.onreadystatechange = () => {
      if (timeout <= 0) {
        this.errorUpload({
          message: "上传超时",
          errorType: UPLOADERROR,
          status: STATIC_TIMEOUT
        });
        xhr.abort(); // 请求中止
      }
      if (Number(xhr.readyState) === 4) {
        clearTimeout(uploadTimer);
        try {
          const responseData = JSON.parse(xhr.responseText);
          this.successUpload && this.successUpload(responseData);
        } catch (err) {
          this.errorUpload({
            message: "上传失败",
            errorType: UPLOADERROR,
            status: STATIC_RESPONSE_ERROR
          });
        }
      }
    };
    xhr.send(formdata);
  }

  /**
   * 把图片转成formdata 可以使用的数据...
   *这里要把\s替换掉..要不然atob的时候会出错....
   */
  dataURLtoBlob(data) {
    const tmp = data.split(",");
    tmp[1] = tmp[1].replace(/\s/g, "");
    const binary = atob(tmp[1]);
    const array = [];
    for (let i = 0; i < binary.length; i++) {
      array.push(binary.charCodeAt(i));
    }
    return this.getBlob(new Uint8Array(array), this.changeType);
  }

  /**
   * 获取blob对象的兼容性写法
   * @param buffer
   * @param format
   * @returns {*}
   */
  getBlob(data, datatype) {
    const _that = this;
    let result;
    try {
      if (datatype) {
        result = new Blob([data], { type: datatype });
      } else {
        result = new Blob(data);
      }
      // 一切正常,直接使用blob.
    } catch (e) {
      const BlobBuilder =
        window.BlobBuilder ||
        window.WebKitBlobBuilder ||
        window.MozBlobBuilder ||
        window.MSBlobBuilder;
      // 使用blobbuilder来生成文件..
      if (e.name === "TypeError" && BlobBuilder) {
        const bob = new BlobBuilder();
        bob.append(data.buffer);
        result = bob.getBlob(datatype);
      } else {
        _that.errorUpload({
          message: "blob转换错误",
          errorType: BLOBCHANGEERROR,
          status: STATIC_EXCHANGE_ERROR
        });
      }
    }

    return result;
  }

  /**
   * 获取formdata
   */
  getFormData() {
    if (window.FormData) {
      return new window.FormData();
    } else {
      return this.formDataShim();
    }
  }

  /**
   * formdata 补丁, 给不支持formdata上传blob的android机打补丁
   * @constructor
   */
  formDataShim() {
    const that = this;
    const parts = []; // Data to be sent
    const boundary =
      Array(5).join("-") + (+new Date() * (1e16 * Math.random())).toString(32);
    const oldSend = XMLHttpRequest.prototype.send;
    // 把xhr的send方法重写一下.
    XMLHttpRequest.prototype.send = function(xhrs) {
      // XMLHttpRequest调用send,传递 formDataShim返回值
      if (xhrs.isSelfFormdata === true) {
        const data = that.getBlob(that.parts);
        const fr = new FileReader();
        fr.onload = () => {
          oldSend.call(this, fr.result);
        };
        fr.onerror = function(err) {
          throw err;
        };
        fr.readAsArrayBuffer(data);
        // 设置content-type
        this.setRequestHeader(
          "Content-Type",
          `multipart/form-data; boundary=${boundary}`
        );
        XMLHttpRequest.prototype.send = oldSend;
      } else {
        oldSend.call(this, xhrs);
      }
    };
    return {
      append: (name, value, filename) => {
        parts.push(
          `\r\n--${boundary}\r\nContent-Disposition: form-data; name="${name}"`
        );

        if (value instanceof Blob) {
          parts.push(
            `; filename="blob"\r\nContent-Type: ${value.type}\r\n\r\n`
          );
          parts.push(value);
        } else {
          parts.push(`\r\n\r\n${value}`);
        }
        parts.push("\r\n");
        // 最后加一下boundary..注意这里一定要在最后加\r\n..否则服务器有可能会解析参数失败..
        parts.push(`\r\n--${boundary}--\r\n`);
        this.parts = parts;
      },
      isSelfFormdata: true
    };
  }
}

var XHRUpload$1 = (...args) => {
  return new XHRUpload(...args);
};

/**
 * 暂不考虑gif
 */

const defaultSize = 2 * 1024 * 1024; // 默认大小2M

/**
 * 给Function 类型的变量赋默认值
 * @param {Function} func
 * @param {*} type
 */
function defaultValue(func, type) {
  return func || (() => undefined);
}

class Upload {
  constructor({
    fileType = /\/(?:jpeg|png|gif|jpg)/i,
    changeType = "image/jpeg",
    elem = null,
    base64 = null,
    maxSize = defaultSize,
    multiple = false,
    beforeUpload,
    uploading,
    successUpload,
    errorUpload,
    xhrParam,
    uploadUrl,
    uploadTimeout = 60,
    formdataName='file',
    fileName='blob'
  }) {
    this.elem = elem;
    this.maxSize = maxSize;
    this.base64 = base64;
    this.beforeUpload = beforeUpload;
    this.uploading = defaultValue(uploading);
    this.successUpload = defaultValue(successUpload);
    this.errorUpload = defaultValue(errorUpload);
    this.errorUpload = defaultValue(errorUpload);
    this.fileType = fileType;
    this.changeType = changeType;
    this.uploadUrl = uploadUrl;
    this.uploadTimeout = uploadTimeout;
    this.fileName = fileName;
    // 上传文件的 name
    this.formdataName = formdataName;
    // 上传的额外参数
    this.xhrParam = xhrParam;
    // 如果传入的是节点,需要调用init拿到将图片文件转为base64
    if (elem) {
      const fileList = elem.files;
      if (fileList && fileList.length) {
        const files = Array.prototype.slice.call(fileList);
        // TODO: 暂时支持单文件上传
        if (!multiple) {
          const file = files[0];
          if (
            this.beforeUpload &&
            this.beforeUpload({ file, type: "file", status: "getFile" }) ===
              false
          ) {
            // 用户传递了beforeUpload 如果返回false 直接中断
            return;
          } else {
            if (!fileType.test(file.type)) {
              this.errorUpload({
                message: '文件类型错误',
                errorType: FILETYPE,
                status: STATIC_FILE_ERROR
              });
              return false;
            }
          }
          this.init(file);
        }
      } else {
        this.errorUpload({
          message: '未获取到上传文件',
          errorType: FILETYPE,
          status: STATIC_FILE_ERROR
        });
      }
    } else if (this.base64) {
      // base64 图片无需获取方向信息，可直接执行压缩上传
      createImage(this.base64)
        .then(image => {
          // 将图片的类型统一转换 指定类型 默认 JPEG，
          this.changeImage(image);
        })
        .catch(error => {
          this.errorUpload({
            message: "创建base64文件失败",
            errorType: IMAGEERROR,
            status: STATIC_EXCHANGE_ERROR
          });
        });
    }
  }

  init(imgFile) {
    const _that = this;
    // 获取照片方向角属性，用户旋转控制
    EXIF.getData(imgFile, function() {
      const Orientation = EXIF.getTag(this, "Orientation");
      _that.Orientation = Orientation;
      _that.getFileResult(imgFile);
    });
  }

  getFileResult(imgFile) {
    const reader = new FileReader();
    reader.readAsDataURL(imgFile);
    reader.onload = eventFile => {
      const { result } = eventFile.target;
      createImage(result)
        .then(image => {
          // 将图片的类型统一转换 指定类型 默认 JPEG，
          this.changeImage(image);
        })
        .catch(error => {
          this.errorUpload({
            message: "blob转换错误",
            errorType: IMAGEERROR,
            status: STATIC_EXCHANGE_ERROR
          });
        });
    };
  }

  changeImage(image) {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    const { width, height } = image;
    canvas.width = width;
    canvas.height = height;
    ctx.fillStyle = "#fff";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(image, 0, 0, width, height);
    if (this.Orientation) {
      // 如果方向角不为1，都需要进行旋转
      switch (this.Orientation) {
        case 6: // 需要顺时针（向左）90度旋转
          canvas.width = height;
          canvas.height = width;
          ctx.rotate((1 * 90 * Math.PI) / 180);
          ctx.drawImage(image, 0, -height, width, height);
          break;
        case 8: // 需要逆时针（向右）90度旋转
          canvas.width = height;
          canvas.height = width;
          ctx.rotate((3 * 90 * Math.PI) / 180);
          ctx.drawImage(image, -width, 0, width, height);
          break;
        case 3: // 需要180度旋转
          ctx.rotate((2 * 90 * Math.PI) / 180);
          ctx.drawImage(image, -width, -height, width, height);
          break;
        default:
          ctx.drawImage(image, 0, 0, width, height);
          break;
      }
    }
    const nbase64 = canvas.toDataURL(this.changeType, 1);
    canvas.width = 0;
    canvas.height = 0;
    // 执行压缩
    Compress({
      base64: nbase64,
      maxSize: this.maxSize,
      fileType: this.changeType
    })
      .then(data => {
        // 压缩成功后对上传最后拦截
        if (data.result) {
          const { base64 } = data;
          if (
            this.beforeUpload &&
            this.beforeUpload({
              file: base64,
              type: "compress",
              status: 200
            }) === false
          ) {
            return;
          }
          this.startUpload(base64);
        }
      })
      .catch(error => {
        console.warn(error);
        this.errorUpload({
          message: "压缩失败",
          errorType: IMAGEERROR,
          status: STATIC_COMPRESS_ERROR
        });
      });
  }

  startUpload(base64) {
    const {
      changeType,
      beforeUpload,
      uploading,
      successUpload,
      errorUpload,
      xhrParam,
      uploadUrl,
      uploadTimeout,
      formdataName,
      fileName,
    } = this;
    XHRUpload$1({
      base64,
      changeType,
      beforeUpload,
      uploading,
      successUpload,
      errorUpload,
      xhrParam,
      uploadUrl,
      uploadTimeout,
      formdataName,
      fileName
    });
  }
}
/**
 * @description 单图片上传集中处理
 * @property {String} uploadUrl 上传文件地址，必传
 * @property {RegExp} fileType 文件类型正则， default: /\/(?:jpeg|png|gif|jpg)/
 * @property {Object} elem example:{files:[file]} 文件域节点 target
 * @property {String} base64 图片base64
 * @property {Number} maxSize 文件size最大限制, default:2 * 1024 * 1024; // 默认大小2M
 * @property {Boolean} multiple 是否多文件 default:false, PS: 不支持多文件
 * @property {Object} xhrParam 上传额外参数
 * @property {String} fileName 上传文件名称 用于new FormDate() append方法第三个参数, default:'blob'
 * @property {String} formdataName 上传文件 name, default:'file'
 * @property {Number} uploadTimeout 执行上传接口时间 单位：s, default:60
 * @property {Function} beforeUpload 上传前调用函数 返回 false 阻止上传
 * @property {Function} uploading 调用上传接口时触发
 * @property {Function} successUpload 调用上传接口成功调用，直接返回接口响应报文JSON格式
 * @property {Function} errorUpload 上传失败回调  {status,errorType, message} 
 * @return ========= 失败详解 =====
 * @type status 502 接口上传超时， 999 响应失败， 1000 上传文件异常， 1001 图片压缩异常， 1002 图片转换异常
 * @type errorType 
 * @typw message 
 */
function YbsUpload(...args) {
  return new Upload(...args);
}

export default YbsUpload;
